package org.mozhu.mboot.core.support.datatable;

import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.collections.MapUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.data.domain.Sort.Direction;
import org.springframework.data.domain.Sort.Order;

import javax.servlet.http.HttpServletRequest;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class DTUtils {

	private static Pattern orderPattern = Pattern.compile("order\\[(\\d+)\\]\\[(column|dir)\\]");
	private static Pattern columnPattern = Pattern.compile("columns\\[(\\d+)\\]\\[(data|name|searchable|orderable)\\]");
	private static Pattern columnSearchPattern = Pattern.compile("columns\\[(\\d+)\\]\\[search\\]\\[(value|regex)\\]");

	public static <T> DTReturnObject<T> toDTReturnObject(Page<T> page) {
		// total number of records (unfiltered)
		long recordsTotal = page.getTotalElements();
		// value will be set when code filters companies by keyword
		long recordsFiltered = page.getTotalElements();
		DTReturnObject<T> dataTableReturnObject = new DTReturnObject<T>();
		dataTableReturnObject.setRecordsTotal(recordsTotal);
		dataTableReturnObject.setRecordsFiltered(recordsFiltered);
		dataTableReturnObject.setData(page.getContent());
		return dataTableReturnObject;
	}

	public static Pageable buildPageable(HttpServletRequest request) {
		if (!isDatatableRequest(request)) {
			return null;
		}
		DTParamModel paramModel = getParamModel(request);
		int size = paramModel.getLength();
		int page = paramModel.getStart() / size;
		Pageable pageable = PageRequest.of(page, size, getSort(paramModel));
		return pageable;
	}

	private static Sort getSort(DTParamModel paramModel) {
		List<Order> orders = getOrders(paramModel);
		if (CollectionUtils.isEmpty(orders)) {
			return null;
		}
		return Sort.by(orders);
	}

	private static List<Order> getOrders(DTParamModel paramModel) {
		List<Order> orders = new ArrayList<>();
		List<DTColumn> columns = paramModel.getColumns();
		List<DTOrder> ordersList = paramModel.getOrders();
		for (int i = 0; i < ordersList.size(); i++) {
			DTOrder dtOrder = ordersList.get(i);
			String columnIndex = dtOrder.getColumn();
			DTColumn column = columns.get(Integer.valueOf(columnIndex));
			if (column != null && StringUtils.isNotBlank(column.getData())) {
				Order order = new Order(Direction.fromString(dtOrder.getDir()), column.getData());
				orders.add(order);
			}
		}
		return orders;
	}

	public static boolean isDatatableRequest(HttpServletRequest request) {
		if (!"POST".equals(request.getMethod())) {
			return false;
		}
		Map<String, String[]> parameterMap = request.getParameterMap();
		if (MapUtils.isEmpty(parameterMap)) {
			return false;
		}
		Set<String> keySet = parameterMap.keySet();
		return keySet.contains("draw") && keySet.contains("start") && keySet.contains("length");
	}

	public static DTParamModel getParamModel(HttpServletRequest request) {
		Map<String, String> parameters = Maps.newHashMap();
		Enumeration<String> enumeration = request.getParameterNames();
		while (enumeration.hasMoreElements()) {
			String key = enumeration.nextElement();
			parameters.put(key, request.getParameter(key));
		}
		return getParamModel(parameters);
	}

	public static DTParamModel getParamModel(Map<String, String> parameters) {
		DTParamModel paramModel = new DTParamModel();
		paramModel.setDraw(parameters.get("draw"));
		paramModel.setSearchValue(parameters.get("search[value]"));
		String searchRegex = parameters.get("search[regex]");
		if (StringUtils.isNotBlank(searchRegex)) {
			paramModel.setSearchRegex(Boolean.valueOf(searchRegex));
		}
		String start = parameters.get("start");
		if (StringUtils.isNotBlank(start)) {
			paramModel.setStart(Integer.valueOf(start));
		}
		String length = parameters.get("length");
		if (StringUtils.isNotBlank(length)) {
			paramModel.setLength(Integer.valueOf(length));
		}
		paramModel.setOrders(getOrderParams(parameters));
		paramModel.setColumns(getParamColumns(parameters));
		return paramModel;
	}

	private static List<DTColumn> getParamColumns(Map<String, String> parameters) {
		Map<Integer, DTColumn> columnsMap = Maps.newHashMap();
		for (Map.Entry<String, String> entry : parameters.entrySet()) {
			String key = entry.getKey();
			String value = entry.getValue();
			if (!key.startsWith("columns") || StringUtils.isEmpty(value)) {
				continue;
			}
			Matcher matcher = columnPattern.matcher(key);
			if (matcher.find()) {
				Integer index = Integer.valueOf(matcher.group(1));
				DTColumn column = columnsMap.get(index);
				if (column == null) {
					column = new DTColumn();
					columnsMap.put(index, column);
				}
				String field = matcher.group(2);
				if ("data".equals(field)) {
					column.setData(value);
				} else if ("name".equals(field)) {
					column.setName(value);
				} else if ("searchable".equals(field)) {
					column.setSearchable(Boolean.valueOf(value));
				} else if ("orderable".equals(field)) {
					column.setOrderable(Boolean.valueOf(value));
				}
			} else {
				matcher = columnSearchPattern.matcher(key);
				if (matcher.find()) {
					Integer index = Integer.valueOf(matcher.group(1));
					DTColumn column = columnsMap.get(index);
					if (column == null) {
						column = new DTColumn();
						columnsMap.put(index, column);
					}
					String field = matcher.group(2);
					if ("value".equals(field)) {
						column.setSearchValue(value);
					} else {
						if (StringUtils.isNotBlank(value)) {
							try {
								column.setSearchRegex(Boolean.valueOf(value));
							} catch (RuntimeException e) {
								// ignore
							}
						}
					}
				}
			}
		}
		List<DTColumn> columns = Lists.newArrayList();
		for (Map.Entry<Integer, DTColumn> entry : columnsMap.entrySet()) {
			Integer key = entry.getKey();
			DTColumn column = entry.getValue();
			columns.add(key, column);
		}
		return columns;
	}

	private static List<DTOrder> getOrderParams(Map<String, String> parameters) {
		Map<Integer, DTOrder> ordersMap = Maps.newHashMap();
		for (Map.Entry<String, String> entry : parameters.entrySet()) {
			String key = entry.getKey();
			String value = entry.getValue();
			if (!key.startsWith("order") || StringUtils.isEmpty(value)) {
				continue;
			}
			Matcher matcher = orderPattern.matcher(key);
			if (!matcher.find()) {
				continue;
			}
			Integer index = Integer.valueOf(matcher.group(1));
			DTOrder order = ordersMap.get(index);
			if (order == null) {
				order = new DTOrder();
				ordersMap.put(index, order);
			}
			String field = matcher.group(2);
			if ("column".equals(field)) {
				order.setColumn(value);
			} else {
				order.setDir(value);
			}
		}
		if (MapUtils.isEmpty(ordersMap)) {
			return Lists.newArrayList();
		}
		List<DTOrder> orders = Lists.newArrayList();
		for (Map.Entry<Integer, DTOrder> entry : ordersMap.entrySet()) {
			Integer key = entry.getKey();
			DTOrder order = entry.getValue();
			orders.add(key, order);
		}
		return orders;
	}

}
